package com.lmh.job.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lmh.common.core.constants.CacheConstants;
import com.lmh.common.core.constants.Constants;
import com.lmh.common.core.enums.ExamListType;
import com.lmh.common.redis.service.RedisService;
import com.lmh.job.domain.exam.Exam;
import com.lmh.job.domain.message.Message;
import com.lmh.job.domain.message.MessageText;
import com.lmh.job.domain.message.vo.MessageTextVO;
import com.lmh.job.domain.user.UserScore;
import com.lmh.job.mapper.exam.ExamMapper;
import com.lmh.job.mapper.user.UserExamMapper;
import com.lmh.job.mapper.user.UserSubmitMapper;
import com.lmh.job.service.IMessageService;
import com.lmh.job.service.IMessageTextService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
public class ExamXxlJob {
    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserSubmitMapper userSubmitMapper;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private IMessageService messageService;

    @Autowired
    private IMessageTextService messageTextService;

    @XxlJob("examListOrganizeHandler")
    public void examListOrganizeHandler() {
        log.info("*** examListOrganizeHandler ***");
        // 未完赛竞赛列表
        List<Exam> unFinishList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .gt(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        refreshCache(unFinishList,CacheConstants.EXAM_UNFINISHED_LIST
                , getExamSetListKey(ExamListType.EXAM_UN_FINISH_LIST.getValue()));

        // 历史列表
        List<Exam> historyList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .le(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));

        refreshCache(historyList, CacheConstants.EXAM_HISTORY_LIST
                ,getExamSetListKey(ExamListType.EXAM_HISTORY_LIST.getValue()));
        log.info("*** examListOrganizeHandler 统计结束 ***");
    }

    @XxlJob(("questionHotListHandler"))
    public void questionHotListHandler() {
        List<Long> questionIdList = userSubmitMapper.selectHostQuestionList();
        if (CollectionUtil.isEmpty(questionIdList)) {
            return;
        }
        redisService.deleteObject(CacheConstants.QUESTION_HOST_LIST);
        redisService.rightPushAll(CacheConstants.QUESTION_HOST_LIST,questionIdList);
    }

    @XxlJob("examResultHandler")
    public void examResultHandler() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime minusDateTime = now.minusDays(1);  // 返回当前时间前一天的时间
        // 获取前一天结束的竞赛的 examId
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle)
                .eq(Exam::getStatus,Constants.TRUE)
                .ge(Exam::getEndTime,minusDateTime)
                .le(Exam::getEndTime,now));
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }
        Set<Long> examIdSet = examList.stream().map(Exam::getExamId).collect(Collectors.toSet());
        List<UserScore> userScoreList = userSubmitMapper.selectUserScoreList(examIdSet);
        // 将 userScoreList 按照 examId 进行分组
        Map<Long, List<UserScore>> userScoreMap = userScoreList.stream()
                .collect(Collectors.groupingBy(UserScore::getExamId));
        createMessage(examList,userScoreMap);
    }

    private void createMessage(List<Exam> examList, Map<Long, List<UserScore>> userScoreMap) {
        List<MessageText> messageTextList = new ArrayList<>();
        List<Message> messageList = new ArrayList<>();
        for (Exam exam : examList) {
            Long examId = exam.getExamId();
            List<UserScore> userScoreList = userScoreMap.get(examId);
            int totalUser = userScoreList.size();
            int examRank = 1;
            String msgTitle = exam.getTitle();
            for (UserScore userScore : userScoreList) {
                userScore.setExamRank(examRank);
                MessageText messageText = new MessageText();
                String content = "您所参与的竞赛：" + exam.getTitle()
                        + "，本次参与竞赛一共" + totalUser + "人， 您排名第"  + examRank + "名！";
                messageText.setMessageTitle(msgTitle);
                messageText.setMessageContent(content);
                messageTextList.add(messageText);
                messageText.setCreateBy(Constants.SYSTEM_USER_ID);
                Message message = new Message();
                message.setSendId(Constants.SYSTEM_USER_ID);
                message.setCreateBy(Constants.SYSTEM_USER_ID);
                message.setRecId(userScore.getUserId());
                messageList.add(message);
                examRank++;
            }
            // 将用户的竞赛排名写入MySQL中
            userExamMapper.updateUserScoreAndRank(userScoreList);
            // 将竞赛的排名信息存储进redis中
            redisService.rightPushAll(getExamRankListKey(examId),userScoreList);
        }
        // 批量插入之后，才会生成text_id,然后需要将text_id插入到对应message行中
        messageTextService.batchInsert(messageTextList);
        Map<String, MessageTextVO> messageTextVOMap = new HashMap<>();
        // 此时 messageList 中的信息就是以每个竞赛的排名来存储的
        for (int i = 0; i < messageList.size(); i++) {
            MessageText messageText = messageTextList.get(i);
            Message message = messageList.get(i);
            message.setTextId(messageText.getTextId());
            String msgDetailKey = getMsgDetailKey(messageText.getTextId());
            MessageTextVO messageTextVO = new MessageTextVO();
            BeanUtil.copyProperties(messageText,messageTextVO);
            messageTextVOMap.put(msgDetailKey, messageTextVO);
        }
        messageService.batchInsert(messageList);
        // redis 操作
        // 将 message 按照recId进行分组
        Map<Long, List<Message>> userMsgMap = messageList.stream()
                .collect(Collectors.groupingBy(Message::getRecId));
        Iterator<Map.Entry<Long, List<Message>>> iterator = userMsgMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, List<Message>> entry = iterator.next();
            Long recId = entry.getKey();
            String userMsgListKey = getUserMsgListKey(recId);
            List<Long> userMsgTextIdList = entry.getValue().stream().map(Message::getTextId)
                    .collect(Collectors.toList());
            redisService.rightPushAll(userMsgListKey,userMsgTextIdList);
        }
        redisService.multiSet(messageTextVOMap);
    }

    public void refreshCache(List<Exam> examList, String examListKey, String[] setKeys) {
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }

        // 将每一个竞赛都存储在redis缓存中
        Map<String, Exam> examMap = new HashMap<>();
        // redis中存储的未完赛的或者历史竞赛，都是存储的是竞赛的examId
        List<Long> examIdList = new ArrayList<>();
        // 将竞赛的开始结束时间作为score，examId作为value存储进set中
        Set<ZSetOperations.TypedTuple<String>> startSet = new HashSet<>();
        Set<ZSetOperations.TypedTuple<String>> endtSet = new HashSet<>();
        for (Exam exam : examList) {
            Long examId = exam.getExamId();
            examMap.put(getDetailKey(examId), exam);
            examIdList.add(examId);
            // 转换时区（假设时间为UTC）
            long startTime = exam.getStartTime().toInstant(ZoneOffset.UTC).toEpochMilli();
            long endTime = exam.getEndTime().toInstant(ZoneOffset.UTC).toEpochMilli();
            // 将开始时间添加到 startSet
            startSet.add(ZSetOperations.TypedTuple.of(String.valueOf(examId), (double) startTime));
            endtSet.add(ZSetOperations.TypedTuple.of(String.valueOf(examId), (double) endTime));
        }
        // 因为redis中存储竞赛信息使用的是string类型，直接set，可以覆盖掉之前的内容
        redisService.multiSet(examMap);  //刷新详情缓存
        // 而未完赛或者历史竞赛在redis中是使用列表的形式存储的，如果不对其先进行清空的话，就会导致列表中出现重复的examId
        // 那么这时就会问了，执行refreshCache()方法的前提条件不是在redis中查询竞赛列表的结果未空吗，这里为啥还需要这一步操作呢
        // 即使在Redis中查询竞赛列表的结果为空，也可能存在旧的缓存数据。为了避免数据重复，需要在更新前清空旧的列表缓存。
        redisService.deleteObject(examListKey);
        redisService.rightPushAll(examListKey, examIdList);      //刷新列表缓存
        redisService.deleteObject(setKeys[0]);
        redisService.deleteObject(setKeys[1]);
        redisService.multiAddZSetObject(setKeys[0],startSet);
        redisService.multiAddZSetObject(setKeys[1],endtSet);
    }

    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }

    private String[] getExamSetListKey(Integer type) {
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(type)) {
            return new String[]{CacheConstants.EXAM_CONTINUE_START_TIME
                    ,CacheConstants.EXAM_CONTINUE_END_TIME};
        }else {
            return new String[]{CacheConstants.EXAM_HISTORY_START_TIME
                    ,CacheConstants.EXAM_HISTORY_END_TIME};
        }
    }

    private String getUserMsgListKey(Long userId) {
        return CacheConstants.USER_MESSAGE_LIST + userId;
    }

    private String getMsgDetailKey(Long textId) {
        return CacheConstants.MESSAGE_DETAIL + textId;
    }

    private String getExamRankListKey(Long examId) {
        return CacheConstants.EXAM_RANK_LIST + examId;
    }
}
